package com.writeEvent;

import com.to.BufferAndChannelTo;
import lombok.extern.slf4j.Slf4j;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

/**
 * @author 罗俊华
 * @date 2021/8/24 - 10:16 上午
 */
@Slf4j
public class SelectorWriteServer {

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress("0.0.0.0", 8081));

            SelectionKey sscSelectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            sscSelectionKey.interestOps(SelectionKey.OP_ACCEPT);
            while (true) {
                log.debug("等待 selector 事件发生");

                int select = selector.select();

                log.debug("触发 selector 事件");

                Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();

                while (selectionKeyIterator.hasNext()) {

                    SelectionKey selectionKey = selectionKeyIterator.next();

                    selectionKeyIterator.remove();

                    if (selectionKey.isAcceptable()) {

                        ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();

                        SocketChannel socketChannel = ssc.accept();

                        socketChannel.configureBlocking(false);

//                          将 selectionKey 注册到 selector 中
                        SelectionKey scSelectionKey = socketChannel.register(selector, SelectionKey.OP_WRITE);

//                        打开一个读取文件的 channel，注意一定要关闭资源
                        FileChannel fileChannel = new RandomAccessFile("/Volumes/extend/下载/archlinux-2021.07.01-x86_64.iso", "r").getChannel();

                        ByteBuffer writeByteBuffer = ByteBuffer.allocate(64 * 1024 * 1024);

//                        将 写缓冲的 byteBuffer 和 读取本地数据的 channel 一起以对象的形式放入附件(attachment)中
                        scSelectionKey.attach(new BufferAndChannelTo(writeByteBuffer, fileChannel));

                        ByteBuffer fileSize = ByteBuffer.allocate(8);
                        fileSize.putLong(fileChannel.size());
                        fileSize.flip();
//                          先向client 写一个整个文件的大小
                        int count = socketChannel.write(fileSize);

                        log.debug("文件的大小为：{}MB，向client先发送文件的大小共计发送{}字节", fileChannel.size() / 1024 / 1024, count);

                    } else if (selectionKey.isWritable()) {

                        log.debug("可写事件触发");
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

//                        从附件（attachment）中获取到 读取本地数据的channel和用于写缓存的 byteBuffer
                        BufferAndChannelTo attachment = (BufferAndChannelTo) selectionKey.attachment();

                        ByteBuffer writeByteBuffer = attachment.getByteBuffer();

                        FileChannel fileChannel = attachment.getFileChannel();

                        writeByteBuffer.flip();

                        if (writeByteBuffer.hasRemaining()) {

                            int write = socketChannel.write(writeByteBuffer);
                            log.debug("往客户端发送【{}】KB的数据，byteBuffer 中还有【{}】KB内容未写", write / 1024, (writeByteBuffer.limit() - writeByteBuffer.position()) / 1024);

                            writeByteBuffer.compact();
                        } else {
                            log.debug("写事件中 byteBuffer 中没有数据可读，现从本地fileChannel中读取到ByteBuffer中");
                            writeByteBuffer.compact();

                            if (fileChannel.read(writeByteBuffer) <= 0) {

                                fileChannel.close();
                                selectionKey.attach(null);
                                selectionKey.interestOps(selectionKey.interestOps() - SelectionKey.OP_WRITE);
                                log.debug("数据已全部写入完成，现取消关注写事件，并取消关联附件，关闭读取本地文件通道");
                            }

                        }
                    }

                }
            }
        }
    }
}
